/* Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.activiti.workflow.simple.converter;

import org.activiti.bpmn.BpmnAutoLayout;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.Process;
import org.activiti.workflow.simple.converter.listener.WorkflowDefinitionConversionListener;
import org.activiti.workflow.simple.converter.step.StepDefinitionConverter;
import org.activiti.workflow.simple.definition.ListStepDefinition;
import org.activiti.workflow.simple.definition.ParallelStepsDefinition;
import org.activiti.workflow.simple.definition.StepDefinition;
import org.activiti.workflow.simple.definition.WorkflowDefinition;
import org.activiti.workflow.simple.exception.SimpleWorkflowException;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Instances of this class are created by a {@link WorkflowDefinitionConversionFactory}.
 * <p>
 * An instance of this class is capabale of doing the actual conversion of a {@link WorkflowDefinition}
 * and it will contain all artifacts produces by the {@link StepDefinitionConverter} objects and
 * {@link WorkflowDefinitionConversionListener} which were injected into
 * the {@link WorkflowDefinitionConversionFactory}.
 *
 * @author Frederik Heremans
 * @author Joram Barrez
 */
public class WorkflowDefinitionConversion {

    // Input
    protected WorkflowDefinition workflowDefinition;

    // Artifacts of the conversion
    protected BpmnModel bpmnModel;
    protected Process process;

    /**
     * It is assumed the conversion will always create a {@link BpmnModel} and a {@link Process}
     * (altough, strictly even that is pluggable). Other artifacts that are produced
     * during conversion are stored in this generic map.
     */
    protected Map<String, Object> additionalArtifacts;

    // Helper members
    protected WorkflowDefinitionConversionFactory conversionFactory;
    protected String lastActivityId;
    protected HashMap<String, Integer> incrementalIdMapping;

    // Properties to influence the conversion
    protected boolean sequenceflowGenerationEnabled = true;
    protected boolean updateLastActivityEnabled = true;

    /* package */
    WorkflowDefinitionConversion(WorkflowDefinitionConversionFactory factory) {
        this.conversionFactory = factory;
    }

    public WorkflowDefinitionConversion(WorkflowDefinitionConversionFactory factory, WorkflowDefinition workflowDefinition) {
        this(factory);
        this.workflowDefinition = workflowDefinition;
    }

    /**
     * Call this method to actually execute the conversion of the {@link WorkflowDefinition}
     * which was provided in the constructor.
     */
    public void convert() {

        if (workflowDefinition == null) {
            throw new SimpleWorkflowException("Cannot start conversion: need to set a WorkflowDefinition first!");
        }

        this.incrementalIdMapping = new HashMap<String, Integer>();
        this.additionalArtifacts = new HashMap<String, Object>();

        // Create new process
        bpmnModel = new BpmnModel();
        process = new Process();
        bpmnModel.addProcess(process);

        // Let conversion listeners know initialization is finished
        if (conversionFactory.getAllWorkflowDefinitionConversionListeners() != null) {
            for (WorkflowDefinitionConversionListener conversionListener : conversionFactory.getAllWorkflowDefinitionConversionListeners()) {
                conversionListener.beforeStepsConversion(this);
            }
        }

        // Convert each step
        convertSteps(workflowDefinition.getSteps());

        // Let conversion listeners know step conversion is done
        if (conversionFactory.getAllWorkflowDefinitionConversionListeners() != null) {
            for (WorkflowDefinitionConversionListener conversionListener : conversionFactory.getAllWorkflowDefinitionConversionListeners()) {
                conversionListener.afterStepsConversion(this);
            }
        }

        // Add DI information to bpmn model
        BpmnAutoLayout bpmnAutoLayout = new BpmnAutoLayout(bpmnModel);
        bpmnAutoLayout.execute();
    }

    public void convertSteps(List<StepDefinition> stepDefinitions) {
        for (StepDefinition step : stepDefinitions) {
            conversionFactory.getStepConverterFor(step).convertStepDefinition(step, this);
        }
    }

    public void convertListParallelSteps(List<ListStepDefinition<ParallelStepsDefinition>> stepDefinitions) {
        for (ListStepDefinition<ParallelStepsDefinition> step : stepDefinitions) {
            conversionFactory.getStepConverterFor(step).convertStepDefinition(step, this);
        }
    }

    /**
     * @param baseName base name of the unique identifier
     * @return a string that can be used as a unique id. Eg. if a baseName with
     * value "userTask" is passed, the first time "userTask1" will be
     * returned. When called agian with the same baseName, "userTask2" is
     * returned. Counts are incremented for each baseName independently
     * withing this context instance.
     */
    public String getUniqueNumberedId(String baseName) {
        Integer index = incrementalIdMapping.get(baseName);
        if (index == null) {
            index = 1;
            incrementalIdMapping.put(baseName, index);
        } else {
            index = index + 1;
            incrementalIdMapping.put(baseName, index);
        }
        return baseName + index;
    }

    /**
     * @return id of the activity that is at the end of the current process. Used
     * to add additional steps and sequence-flows to the process.
     */
    public String getLastActivityId() {
        return this.lastActivityId;
    }

    /**
     * @param lastActivityId id of the activity that is at the end of the current process. Used
     *                       to add additional steps and sequence-flows to the process.
     */
    public void setLastActivityId(String lastActivityId) {
        this.lastActivityId = lastActivityId;
    }

    public BpmnModel getBpmnModel() {
        return bpmnModel;
    }

    public void setBpmnModel(BpmnModel bpmnModel) {
        this.bpmnModel = bpmnModel;
    }

    public Process getProcess() {
        return process;
    }

    public void setProcess(Process process) {
        this.process = process;
    }

    public Object getArtifact(String artifactKey) {
        return additionalArtifacts.get(artifactKey);
    }

    public Map<String, Object> getAdditionalArtifacts() {
        return additionalArtifacts;
    }

    public void setArtifact(String artifactKey, Object artifact) {
        additionalArtifacts.put(artifactKey, artifact);
    }

    public WorkflowDefinition getWorkflowDefinition() {
        return workflowDefinition;
    }

    public void setWorkflowDefinition(WorkflowDefinition workflowDefinition) {
        this.workflowDefinition = workflowDefinition;
    }

    public boolean isSequenceflowGenerationEnabled() {
        return sequenceflowGenerationEnabled;
    }

    public void setSequenceflowGenerationEnabled(boolean sequenceflowGenerationEnabled) {
        this.sequenceflowGenerationEnabled = sequenceflowGenerationEnabled;
    }

    public boolean isUpdateLastActivityEnabled() {
        return updateLastActivityEnabled;
    }

    public void setUpdateLastActivityEnabled(boolean updateLastActivityEnabled) {
        this.updateLastActivityEnabled = updateLastActivityEnabled;
    }

    public WorkflowDefinitionConversionFactory getConversionFactory() {
        return conversionFactory;
    }

    /**
     * Returns the BPMN 2.0 xml which is the converted version of the
     * provided {@link WorkflowDefinition}.
     */
    public String getBpmn20Xml() {
        if (bpmnModel == null) {
            convert();
        }
        BpmnXMLConverter bpmnXMLConverter = new BpmnXMLConverter();
        return new String(bpmnXMLConverter.convertToXML(bpmnModel));
    }
}
